package com.y.report.util;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * 数据打印工具,支持控制台和日志打印,支持数据左对齐，居中和右对齐，默认右对齐。小标题自动居中(不支持设置)
 * <br>备注:大标题可以使用中文，小标题和内容不建议包含中文
 */
public class DataPrinter {

    public DataPrinter() {
    }

    public DataPrinter(final LogOutputModel logOutputModel, final DataAlignModel dataAlignModel, final boolean showRows) {
        this.logOutputModel = logOutputModel;
        this.dataAlignModel = dataAlignModel;
        this.showRows = showRows;
    }

    public LogOutputModel getLogOutputModel() {
        return this.logOutputModel;
    }

    public void setLogOutputModel(final LogOutputModel logOutputModel) {
        this.logOutputModel = logOutputModel;
    }

    public DataAlignModel getDataAlignModel() {
        return this.dataAlignModel;
    }

    public void setDataAlignModel(final DataAlignModel dataAlignModel) {
        this.dataAlignModel = dataAlignModel;
    }

    public boolean isShowRows() {
        return this.showRows;
    }

    public void setShowRows(final boolean showRows) {
        this.showRows = showRows;
    }

    private static Logger log = LoggerFactory.getLogger(DataPrinter.class);

    /**
     * 日志输出模式(默认控制台输出)
     */
    private LogOutputModel logOutputModel = LogOutputModel.CONSOLE;

    /**
     * 数据对齐模式(默认右对齐)
     */
    private DataAlignModel dataAlignModel = DataAlignModel.RIGHT;

    /**
     * 是否显示行数
     */
    private boolean showRows = true;

    /**
     * 日志输出模式
     */
    public enum LogOutputModel {
        LOG, CONSOLE;
    }

    /**
     * 数据对齐模式
     */
    public enum DataAlignModel {
        LEFT, CENTER, RIGHT;
    }


    /**
     * 数据格式化输出
     *
     * @param topTitle 大标题
     * @param titles   小标题
     * @param data     数据集合
     * @param line     显示行数
     */
    public void printListMap(String topTitle, String[] titles, List<Map<String, Object>> data, Integer line) {
        List<Object[]> objsList = new ArrayList<>();
        if (null != data) {
            for (Map<String, Object> map : data) {
                Object[] objs = new Object[titles.length];
                for (int i = 0; i < titles.length; i++) {
                    Object obj = map.get(titles[i]);
                    objs[i] = obj;
                }
                objsList.add(objs);
            }
        }
        print(topTitle, titles, objsList, line);
    }


    /**
     * 数据格式化输出
     *
     * @param topTitle 大标题
     * @param data     数据集合
     * @param line     显示行数
     */
    public void print(String topTitle, List<Map<String, Object>> data, Integer line) {
        Pair<List<String>, List<List<Object>>> tuple2 = format(data, line);
        List<String> titleList = tuple2.getKey();
        List<List<Object>> valueList = tuple2.getValue();

        List<Object[]> collect = valueList.stream()
                .limit(line)
                .map(o -> o.toArray(new Object[titleList.size()]))
                .collect(Collectors.toList());

        print(topTitle, titleList.toArray(new String[titleList.size()]), collect, line);
    }


    /**
     * 数据格式化输出
     *
     * @param topTitle 大标题
     * @param titles   小标题
     * @param data     数据集合
     * @param line     显示行数
     */
    public void print(String topTitle, String[] titles, List<Object[]> data, Integer line) {
        line = null == line ? 3 : line;
        topTitle = null == topTitle ? "Not Defined" : topTitle;

        /**
         * 控制日志输出
         */
        Function<String, Integer> outputLog_fun = (string) -> {
            if (this.getLogOutputModel() == LogOutputModel.CONSOLE) {
                System.out.println(string);
            } else if (this.getLogOutputModel() == LogOutputModel.LOG) {
                log.info(string);
            }
            return 1;
        };

        /**
         * 1. 根据标题和内容计算每一列需要的最大宽度
         */
        Integer[] lineWidths = new Integer[titles.length];
        int totalWidth = 0;//最大长度
        for (int i = 0; i < lineWidths.length; i++) {
            int maxWidth = 0; // 默认最小长度
            String title = titles[i];
            if (null != title) {
                maxWidth = Math.max(maxWidth, title.length());
            } else {
                titles[i] = "";
            }

            if (null != data) {
                for (Object[] array : data) {
                    Object obj = array[i];
                    if (null != obj) {
                        maxWidth = Math.max(maxWidth, obj.toString().length());
                    }
                }
            }
            if (maxWidth + 4 < 10) {
                maxWidth = 10;
            } else {
                maxWidth = maxWidth + 4; //避免数据左右顶到表格
            }

            lineWidths[i] = maxWidth;
            totalWidth = totalWidth + maxWidth;
        }

        /**
         * 2. 输出大标题
         */
        if (isShowRows()) {
            int dataSize = null != data ? data.size() : 0;
            outputLog_fun.apply(StrUtil.padPre(topTitle + "(rows:" + dataSize + ")", (int) (totalWidth * 0.5), ' '));
            //outputLog_fun.apply(StringUtils.leftPad(topTitle + "(rows:" + dataSize + ")", (int) (totalWidth * 0.5)));
        } else {
            outputLog_fun.apply(StrUtil.padPre(topTitle, (int) (totalWidth * 0.5), ' '));
            //outputLog_fun.apply(StringUtils.leftPad(topTitle, (int) (totalWidth * 0.5)));
        }

        /**
         * 3. 输出标题头
         */
        StringBuffer head_sb = new StringBuffer("+");
        for (int i = 0; i < titles.length; i++) {
            String title = titles[i];
            String str = StrUtil.center(title, lineWidths[i], "-");
            //String str = StringUtils.center(title, lineWidths[i], "-");
            head_sb.append(str).append("+");
        }
        outputLog_fun.apply(head_sb.toString());

        /**
         * 4. 输出数据
         */

        if (null != data) {
            for (int i = 0; i < data.size(); i++) {
                Object[] array = data.get(i);
                StringBuffer data_sb = new StringBuffer("|");
                for (int j = 0; j < array.length; j++) {
                    Object obj = array[j];
                    String str = null;
                    if (this.getDataAlignModel() == DataAlignModel.RIGHT) {
                        str = String.format("%" + (lineWidths[j] - 2) + "s  ", obj);
                    } else if (this.getDataAlignModel() == DataAlignModel.CENTER) {
                        str = StrUtil.center(obj + "", lineWidths[j]);
                        //str = StringUtils.center(obj + "", lineWidths[j]);
                    } else if (this.getDataAlignModel() == DataAlignModel.LEFT) {
                        str = String.format("  %-" + (lineWidths[j] - 2) + "s", obj);
                    }

                    data_sb.append(str).append("|");
                }
                outputLog_fun.apply(data_sb.toString());
            }
        }

        /**
         * 5.输出结尾
         */
        StringBuffer foot_sb = new StringBuffer("+");
        for (int i = 0; i < titles.length; i++) {
            String str = StrUtil.center("", lineWidths[i], "-");
            //String str = StringUtils.center("", lineWidths[i], "-");
            foot_sb.append(str).append("+");
        }
        outputLog_fun.apply(foot_sb.toString());
    }


    public static Pair<List<String>, List<List<Object>>> format(List<Map<String, Object>> dataList, Integer line) {
        line = line == null ? 3 : line;
        List<String> titles = new ArrayList<>();
        List<List<Object>> lines = new ArrayList<>();

        //add keys
        dataList.stream()
                .limit(line)
                .forEach(x -> x.entrySet()
                        .stream()
                        .filter(y -> !titles.contains(y.getKey()))
                        .forEach(y -> titles.add(y.getKey()))
                );

        //add values
        dataList.stream()
                .limit(line)
                .forEach(x -> lines.add(
                                titles.stream()
                                        .map(x::get)
                                        .collect(Collectors.toList())
                        )
                );

        return Pair.of(titles, lines);
    }
}
